<?php
/*
 * Glorious Framework
 *
 * Form Validator class
 *
 * (c) 2007-2010 Konspire Design
 *
 * $Id: form_validator.php,v f261dc61ec42 2010/05/06 12:40:33 and $
 * $Author: and $
 * $Date: 2010/05/06 12:40:33 $
*/

load_helper('form');
load_helper('file');

class Form_validator {

	protected $nonce_action = '';
	protected $error_format = '<p class="error">%s</p>';
	protected $validator_errors = array();
	protected $errors = array();
	protected $rules = array();
	protected $allowed_php_filters = array(
			'trim', 'ltrim', 'rtrim',
			'intval', 'abs', 'floatval', 'doubleval');

	public function __construct() {
		$this->validator_errors = array();

		$this->validator_errors['required'] = __('The :label field is required.');
		$this->validator_errors['matches'] = __('The :field1 field does not match the :field2 field.');
		$this->validator_errors['min_length'] = __('The :label field must be at least :len characters in length.');
		$this->validator_errors['max_length'] = __('The :label field must be not more than :len characters in length.');
		$this->validator_errors['exact_length'] = __('The :label field must be exactly :len characters in length.');
		$this->validator_errors['regexp'] = __('The :label field has invalid format or contains restricted characters.');
		$this->validator_errors['valid_email'] = __('The :label field contains not well formed E-mail address.');
		$this->validator_errors['valid_emails'] = __('The :label field contains not well formed comma-separated list of E-mail addresses.');
		$this->validator_errors['valid_base64'] = __('The :label field contains invalid base64 data.');
		$this->validator_errors['valid_ip'] = __('The :label field contains invalid IP address.');

		$this->validator_errors['valid_number'] = __('The :label field contains not well formed number value.');
		$this->validator_errors['min_number'] = __('The :label field must be higher than :num.');
		$this->validator_errors['max_number'] = __('The :label field must be lower than :num.');

		$this->validator_errors['file_required'] = __('The :label field is required. Upload error: :error');
		$this->validator_errors['valid_filetype'] = __('The :label field contains not allowed file format. Allowed file formats are: :formats.');
		$this->validator_errors['max_filesize'] = __('The :label field exceeds maximum allowed file size in :maxsize, uploaded file size is :filesize.');

		$this->validator_errors['valid_money'] = __('The :label field must be a money amount.');
	}

	public function reset() {
		$this->rules = array();
		$this->errors = array();
	}

	public function set_error_format($format) {
		$this->error_format = $format;
	}

	public function format_errors($format = '', $field = '') {
		if(empty($format))
			$format = $this->error_format;

		$str = '';

		if($field === '') {
			foreach($this->errors as $errfield => $errstr)
				$str .= sprintf($this->error_format, $errstr) . "\n";
		} else {
			if(isset($this->errors[$field]))
				$str = sprintf($this->error_format, $this->errors[$field]);
		}

		return $str;
	}

	public function set_error($field_name, $error) {
		if(!empty($field_name))
			$this->errors[$field_name] = $error;
		else
			$this->errors[] = $error;
	}

	public function has_error($field_name) {
		return isset($this->errors[$field_name]);
	}

	public function get_error($field_name) {
		if($this->has_error($field_name))
			return $this->errors[$field_name];

		return false;
	}

	public function set_rules($field_name, $field_label, $rules) {
		$this->rules[$field_name] = array(
				'label' => $field_label,
				'rules' => $rules);
	}

	public function get_rules($field_name = '') {
		if($field_name === '')
			return $this->rules;
		else if(isset($field_name))
			return $this->rules[$field_name];

		return false;
	}

	public function get_label($field_name) {
		if(isset($this->rules[$field_name]))
			return $this->rules[$field_name]['label'];

		return false;
	}

	public function validator_error($validator, $fields = array()) {
		$pattern = '';
		
		if(isset($this->validator_errors[$validator]))
			$pattern = $this->validator_errors[$validator];
		
		if(empty($fields))
			return $pattern;

		return _::replace_pattern($pattern, $fields);
	}

	public function get_validator_errors() {
		return $this->validator_errors;
	}

	public function set_validator_error($validator, $str) {
		$this->validator_errors[$validator] = $str;
	}

	protected function normalize_attr($str) {
		return str_replace(array('-', '['), '_', preg_replace( '#[^a-z0-9_\[-]+#i', '', $str));
	}

	protected function get_real_fieldname($field_name) {
		$form_prefix = $this->normalize_attr($this->nonce_action);
		if($form_prefix != '')
			return preg_replace('#^(\w+)(.*?)$#i', preg_quote($form_prefix) . '[$1]$2', $field_name);

		return $field_name;
	}

	public function get_form_fieldid($field_name) {
		$form_prefix = $this->normalize_attr($this->nonce_action);
		if($form_prefix != '')
			return $form_prefix . '_' . $field_name;

		return $field_name;
	}

	public function &field_value($field_name, $where = 'post') {

		$where = strtolower($where);

		$where = $where == 'get' ? $_GET : ($where == 'files' ? $_FILES : $_POST);

		$real_field_name = $this->get_real_fieldname($field_name);

		$field_value =& array_traverse($real_field_name, $where, null);

		return $field_value;
	}

	public function run($action = '') {

		$this->nonce_action = $action;

		if(!empty($action)) {
			$post_action = Request::instance()->req('action');

			if($post_action != $action)
				return false;

			if(!Nonce::instance()->check_nonce($action)) {
				$this->errors[] = __('Invalid nonce, try again?');
				return false;
			}
		}

		foreach($this->rules as $field_name => $item) {
			// POST value passed by ref so it will be changed if you've changed it
			//$real_field_name = $this->get_real_fieldname($field_name);
			//$field_value =& array_traverse($real_field_name, $_POST, null);

			$field_value =& $this->field_value($field_name);

			foreach($item['rules'] as $k => $v) {
				// Process key/value pairs as rule/value for rule
				// or if the key is just number so no value provided

				$call_func = null;
				$call_arg = null;

				if(is_int($k)) {
					// callback
					if(is_array($v) && isset($v['callback'])) {
						$call_func = $v['callback'];
						$v['validator'] =& $this;
						unset($v['callback']);
						$call_arg = $v;
					} elseif(in_array($v, $this->allowed_php_filters)) {
						// check for php filter functions
						$field_value = $v($field_value);
						continue;
					} else {
						// validators without argument expected
						$call_func = array($this, $v);
						$call_arg = null;
					}
				}
				else {
					// any validators with argument expected
					$call_func = array($this, $k);
					$call_arg = $v;
				}

				if(is_callable($call_func) &&
						!call_user_func($call_func, $field_name, $field_value, $call_arg))
					break 1;
			}
		}

		if(!sizeof($this->errors))
			return true;

		return false;
	}

	/*
     * Internal validation functions
	*/

	public function required($field_name, $field_value) {
		if(!is_array($field_value))
			$field_value = trim($field_value);

		if(is_array($field_value) && !empty($field_value))
			return true;
		else if($field_value != '')
			return true;

		$label = $this->get_label($field_name);
		$this->set_error($field_name, $this->validator_error('required', compact('label')));

		return false;
	}

	public function matches($field_name, $field_value, $field_name2) {

		$real_field_name2 = $this->get_real_fieldname($field_name2);
		
		$result = array_traverse($real_field_name2, $_POST, null);

		if(!is_null($result) && $result == $field_value)
			return true;

		$field1 = $this->get_label($field_name);
		$field2 = $this->get_label($field_name2);

		$this->set_error($field_name, $this->validator_error('matches', compact('field1', 'field2')));

		return false;
	}

	public function min_length($field_name, $field_value, $len) {

		if(empty($field_value) || mb_strlen($field_value) >= $len)
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('min_length', compact('label', 'len')));

		return false;
	}

	public function max_length($field_name, $field_value, $len) {
		if(empty($field_value) || mb_strlen($field_value) <= $len)
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('max_length', compact('label', 'len')));

		return false;
	}

	public function exact_length($field_name, $field_value, $len) {
		if(empty($field_value) || mb_strlen($field_value) == $len)
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('exact_length', compact('label', 'len')));

		return false;
	}

	public function regexp($field_name, $field_value, $regexp) {
		if(empty($field_value) || preg_match($regexp, $field_value))
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('regexp', compact('label')));

		return false;
	}

	public function is_valid_email($email) {
		return preg_match("/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ix", $email);
	}

	public function valid_email($field_name, $field_value) {
		if(empty($field_value) || $this->is_valid_email($field_value))
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('valid_email', compact('label')));

		return false;
	}

	public function valid_emails($field_name, $field_value) {
		if (strpos($field_value, ',') === false)
			return $this->valid_email($field_name, trim($field_value));

		foreach(explode(',', $field_value) as $email) {
			if (trim($email) != '' && !$this->is_valid_email(trim($email))) {

				$label = $this->get_label($field_name);

				$this->set_error($field_name, $this->validator_error('valid_emails', compact('label')));

				return false;
			}
		}

		return true;
	}

	public function valid_base64($field_name, $field_value) {
		if(!preg_match('/[^a-zA-Z0-9\/\+=]/', $field_value))
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('valid_base64', compact('label')));

		return false;
	}

	public function valid_ip($field_name, $field_value) {
		$bad_ip = true;
		$ip_segments = explode('.', $field_value);

		// Always 4 segments needed
		if(sizeof($ip_segments) == 4) {
			// IP can not start with 0
			if($ip_segments[0][0] != '0') {
				$bad_ip = false;
				// Check each segment
				foreach($ip_segments as $segment) {
					// IP segments must be digits and can not be
					// longer than 3 digits or greater then 255
					if($segment == '' || preg_match("/[^0-9]/", $segment) || $segment > 255 || strlen($segment) > 3) {
						$bad_ip = true;
						break;
					}
				}
			}
		}

		if($bad_ip) {
			$label = $this->get_label($field_name);

			$this->set_error($field_name, $this->validator_error('valid_ip', compact('label')));
		}

		return !$bad_ip;
	}

	public function valid_number($field_name, $field_value) {

		if(empty($field_value) || is_numeric($field_value))
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('valid_number', compact('label')));

		return false;
	}

	public function min_number($field_name, $field_value, $num) {

		if(empty($field_value) || (is_numeric($field_value) && $field_value >= $num))
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('min_number', compact('label', 'num')));

		return false;
	}

	public function max_number($field_name, $field_value, $num) {
		if(is_numeric($field_value) && $field_value <= $len)
			return true;

		$label = $this->get_label($field_name);

		$this->set_error($field_name, $this->validator_error('max_number', compact('label', 'num')));

		return false;
	}

	public function file_required($field_name, $field_value) {

		$file = $this->field_value($field_name, 'files');

		if(_::is_upload_error($file, false)) {

			$label = $this->get_label($field_name);
			$error = _::upload_error($file);
			
			$this->set_error($field_name, $this->validator_error('file_required', compact('label', 'error')));

			return false;
		}

		return true;
	}

	public function valid_filetype($field_name, $field_value, $fileformats) {

		$file = $this->field_value($field_name, 'files');

		$label = $this->get_label($field_name);
		$formats = is_array($fileformats) ? array_values($fileformats) : explode(',', $fileformats);

		if(is_array($file) &&
		   isset($file['name']) &&
		   !_::is_upload_error($file, false))
		{

			$is_ok = false;
			$file_ext = '';

			$pos = mb_strrpos($file['name'], '.');
			
			if($pos !== false && $pos+1 < mb_strlen($file['name'])){
				$file_ext = mb_substr($file['name'], $pos+1);
			}

			foreach($formats as $ext) {
				if($file_ext == trim(strtolower($ext))) {
					$is_ok = true;
					break;
				}
			}

			if(!$is_ok) {
				$formats = strtoupper(implode(', ', $formats));
				$this->set_error($field_name, $this->validator_error('valid_filetype', compact('label', 'formats')));
			}

			return $is_ok;
		}


		return true;
	}

	public function max_filesize($field_name, $field_value, $maxsize) {

		$file = $this->field_value($field_name, 'files');
		
		$label = $this->get_label($field_name);

		if(is_array($file) &&
		   isset($file['size']) &&
		   !_::is_upload_error($file, false))
		{

			$filesize = (int)$file['size'];
			$maxsize = (int)$maxsize;
			
			if($filesize > $maxsize) {
				$this->set_error($field_name, $this->validator_error('max_filesize',
						array('label' => $label, 
							  'maxsize' => size_format($maxsize),
							  'filesize' => size_format($filesize))
					));
				return false;
			}
		}
		
		
		return true;

	}
	
	public function jquery_rules($rules,$nonce_action='') {
		
		$this->nonce_action = $nonce_action;
		
		$r = array();
		//if ( isset($rules['matches']) ) { var_dump($rules);die; }
		
		$tr = array(
			'required' => array( 'required' => true ),
			'matches' => array( 'equalTo' => create_function('$params,$validator','return "#".$validator->get_form_fieldid($params[0]);') ),
			'min_length' => array( 'minlength' => ':p0' ),
			'max_length' => array( 'maxlength' => ':p0' ),
			'exact_length' => array( 'rangelength' => array( ':p0', ':p0' ) ),
//<([A-Z][A-Z0-9]*)\b[^>]*>(.*?)</\1>
			'regexp' => array( 'regex' => create_function('$params,$validator','return preg_replace("@^(\@|#|/)(.*)(\\\1[ig]*)$@","\\\2",$params[0]);') ),
			'valid_email' => array( 'email' => true ),
//			'valid_emails' => array( 'emails' => true ),
//			'valid_base64' => null,
//			'valid_ip' => null,
			'valid_number' => array( 'number' => true ),
			'min_number' => array( 'min' => ':p0' ),
			'max_number' => array( 'max' => ':p0' ),
			'file_required' => array( 'required' => true ),
			'valid_filetype' => array( 'accept' => ':p0' ),
//			'max_filesize' => null,
//			'valid_money' => null,
			);
		
/*
		remote: "Please fix this field.",
		date: "Please enter a valid date.",
		dateISO: "Please enter a valid date (ISO).",
		digits: "Please enter only digits.",
		creditcard: "Please enter a valid credit card number.",
		range: $.validator.format("Please enter a value between {0} and {1}."),
*/

		foreach( $rules as $rule => $params ) {
	
			if (ctype_digit((string)($rule))&&is_string($params)) {
				$rule = $params;
				$params = null;
			}
			
			if (in_array($rule,$this->allowed_php_filters) || $rule=='messages')
				continue;
			
			if (!isset($tr[$rule]))
				continue;//$rule = array( $rule => $params?$params:true );
			else {
				$rule = $tr[$rule];
				$params = (array)($params);
				foreach( array_keys($rule) as $k )
					$rule[$k] = $this->_js_rule_format($rule[$k],$params);
			}
			
			$r += $rule;
		}

		if(isset($rules['messages'])) {

			$messages = array();

			foreach(array_keys($rules['messages']) as $k)
				if(isset($tr[$k])) {
					$tkeys = array_keys($tr[$k]);
					$messages[reset($tkeys)] = $rules['messages'][$k];
				}

			$r['messages'] = $messages;
		}

		return $r;
	}
	
	private function _js_rule_format($v,$params=null)
	{
		$params = (array)($params)+array(0,0,0,0,0);

		if (is_array($v)) {
			foreach (array_keys($v) as $j) $v[$j] = $this->_js_rule_format($v[$j],$params);

		} elseif (is_callable($v)) {
			$v = $v($params,$this);

		} elseif (!is_bool($v)) {
			$v = tool::subst( $v, $params, ':p%s' );

		} elseif (is_bool($v)) ;
			// nothing to do

		return $v;
	}

}

/* End of file form_validator.php */
/* Location: ./system/helpers/form_validator.php */